package com.stcc.base.db;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class}
)})
@Slf4j
public class PaginationStatementHandlerInterceptor implements Interceptor {
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    Dialect dialect = null;

    public PaginationStatementHandlerInterceptor() {
    }
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        ParameterHandler parameterHandler = statementHandler.getParameterHandler();
        BoundSql boundSql = statementHandler.getBoundSql();
        MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
        RowBounds rowBounds = (RowBounds)metaStatementHandler.getValue("delegate.rowBounds");
        if (rowBounds != null && rowBounds != RowBounds.DEFAULT) {
            String originalSql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
            Page<?> page = (Page)rowBounds;
            int offset = page.getOffset();
            int limit = page.getLimit();
            if (-1 < offset && -1 < limit) {
                String countSql = this.dialect.getCountString(originalSql);
                Connection connection = (Connection)invocation.getArgs()[0];
                int total = this.getTotal(parameterHandler, connection, countSql);
                page.setTotalCount(total);
                metaStatementHandler.setValue("delegate.boundSql.sql", this.dialect.getLimitString(originalSql, page.getOffset(), page.getLimit()));
            }

            metaStatementHandler.setValue("delegate.rowBounds.offset", 0);
            metaStatementHandler.setValue("delegate.rowBounds.limit", 2147483647);
            if (log.isDebugEnabled()) {
                log.debug("分页SQL : " + boundSql.getSql());
            }

            return invocation.proceed();
        } else {
            return invocation.proceed();
        }
    }

    public Object plugin(Object target) {
        return target instanceof StatementHandler ? Plugin.wrap(target, this) : target;
    }

    public void setProperties(Properties properties) {
        String dialectClass = properties.getProperty("dialectClass");

        try {
            this.dialect = (Dialect)Class.forName(dialectClass).newInstance();
        } catch (Exception var4) {
            throw new RuntimeException("cannot create dialect instance by dialectClass:" + dialectClass, var4);
        }
    }

    private int getTotal(ParameterHandler parameterHandler, Connection connection, String countSql) throws Exception {
        PreparedStatement prepareStatement = connection.prepareStatement(countSql);
        parameterHandler.setParameters(prepareStatement);
        ResultSet rs = prepareStatement.executeQuery();
        int count = 0;
        if (rs.next()) {
            count = rs.getInt(1);
        }

        rs.close();
        prepareStatement.close();
        return count;
    }
}
